/*
 * Android NewsFeed Application is a project for the Technological Educational Institute of Athens.
 * It's purpose is to familiarise the Android SDK and development procedures to students.
 * 
 * 
 * Android NewsFeed version 0.00
 * 
 * $LastChangedDate: 2011-08-11 14:48:40 +0300 (Thu, 11 Aug 2011) $
 * $Author: snoopaki $
 * $Rev: 40 $
 */
package gr.teiath.newsfeed;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

public class NewsfeedApplication extends Application implements OnSharedPreferenceChangeListener {

	private static final String tag = NewsfeedApplication.class.getSimpleName();
	
	private static PendingIntent ALARM_SERVICE_INTENT;
	private static boolean NETWORK_CONNECTION_ENABLED = false;
	private static AlarmManager ALARM_MANAGER;
	
	// Preferences
	private static SharedPreferences SHARED_PREFERENCES;
	private static boolean PREF_ALARM_CHANGED = false;
	private static boolean PREF_NETWORK_CHANGED = false;
	private static boolean PREF_CHANGED = false;
	
	private static final String WHERE_ID_IS = "_id = ?"; 
	
	private DBHelper dbHelper;
	private SQLiteDatabase db;
	
	@Override
	public void onCreate() {
		super.onCreate();
		SHARED_PREFERENCES = PreferenceManager.getDefaultSharedPreferences(this);
		SHARED_PREFERENCES.registerOnSharedPreferenceChangeListener(this);
		ALARM_MANAGER = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
		
		Log.i(tag, "****** Newsfeed APP // onCreate");
	}

	@Override
	public void onTerminate() {
		super.onTerminate();
		
		Log.i(tag, "****** Newsfeed APP // onTerminate");
	}

	public synchronized void createDB() {
		dbHelper = new DBHelper(this); 
        try {
        	dbHelper.createDataBase();
        	
        	Log.i(tag, "****** Newsfeed APP // Check/Create database");
	 	} catch (IOException ioe) {
	 		throw new Error("****** Unable to create database");
	 	}
        
        dbHelper = null; // 4GC
	
	}
	/*
	private Cursor readQuery(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();

		Log.i(tag, "****** Newsfeed APP // Running readQuery()");
		
		Cursor cur = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
		cur.moveToFirst();

		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		
		return cur;
	}
	
	private void updateQuery(String table, ContentValues values, String whereClause, String[] whereArgs) {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getWritableDatabase();
		
		Log.i(tag, "****** Newsfeed APP // Running updateQuery()");

		db.update(table, values, whereClause, whereArgs);
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
	}
	*/
	
	public synchronized void purgeDatabase(boolean purgeAll) {
		dbHelper = new DBHelper(this); 

		if(purgeAll) {
			db = dbHelper.getWritableDatabase();
			db.delete("news", null, null);
		} else {
			db = dbHelper.getReadableDatabase();
			
			Cursor cur = db.query("news", new String[] {"_id"}, null, null, null, null, "date DESC", "3");
			cur.moveToFirst();
			
			db.close();		
			
			String whereClause = "_id=? OR _id=? OR _id=?";
			String[] whereArgs = new String[3];
			
			int i=0;
			
			while(!cur.isAfterLast()) {
				whereArgs[i] = cur.getString(0);
				cur.moveToNext();
				i++;
			}
			cur.close();
			
			db = dbHelper.getWritableDatabase();
			db.delete("news", whereClause, whereArgs);
		}
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
	}
	
	public synchronized void purgePrefs() {
		SharedPreferences.Editor prefEditor = SHARED_PREFERENCES.edit();
		prefEditor.remove("facultyPrefs");
		prefEditor.commit();	
	}
	/*
	public synchronized Cursor readNews() {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();
		final SQLiteQueryBuilder dbQuery = new SQLiteQueryBuilder();
		
		String[] cols = {
				"news._id AS _id", 
				"news.name AS name",
				"news.date AS date",
				"news.link AS link",
				"news._content AS _content",
				"news._date AS _date",
				"news._external AS _external",
				"news.faculty_id AS faculty_id",
				"faculty.name AS faculty_name",
				"faculty.p_type AS p_type"
		};
		String whereClause = "news.faculty_id=?";
		String[] faculty_id = this.LoadFaculties(this).toArray(new String[0]);
		for (int i=1;i<faculty_id.length;i++) whereClause += " OR news.faculty_id=?"; // TODO ?? leave hack or make rawSQL statement
		
		
		dbQuery.setTables("news LEFT OUTER JOIN faculty ON (news.faculty_id = faculty._id)");
		//Cursor cursor = db.query("news", null, whereClause, faculty_id, null, null, "date DESC, _id DESC", null);
		Cursor cursor = dbQuery.query(db, cols, whereClause, faculty_id, null, null, "date DESC, _id DESC", null);
		cursor.moveToFirst();
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		
		return cursor;
	}*/
	public synchronized long countNews() {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();

		String[] cols = { "count()" };
		String whereClause = "news.faculty_id=?";
		String[] faculty_id = this.LoadFaculties(this).toArray(new String[0]);
		for (int i=1;i<faculty_id.length;i++) whereClause += " OR news.faculty_id=?"; // TODO ?? leave hack or make rawSQL statement
		
		//Cursor cursor = db.query("news", null, whereClause, faculty_id, null, null, "date DESC, _id DESC", null);
		Cursor cursor = db.query("news", cols, whereClause, faculty_id, null, null, null);
		cursor.moveToFirst();
		
		long count = cursor.getLong(0);
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		cursor.close();
		
		return count;
		
	}
	
	public synchronized Cursor readNewsList(int lastItem) {
		NewsList.NEWS_COUNT = countNews(); // UPDATE NewsList NEWS_COUNT static variable
		
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();

		final SQLiteQueryBuilder dbQuery = new SQLiteQueryBuilder();
		final int LIMIT = 20;
		final String PAGE ="0," + String.valueOf(LIMIT + lastItem);
		
		Log.i(tag,"****** Newsfeed APP // NewsList SQL LIMIT: " + PAGE);
		String[] cols = {
				"news._id AS _id", 
				"news.name AS name",
				"news.date AS date",
				"news.link AS link",
				"news._content AS _content",
				"news._date AS _date",
				"news._external AS _external",
				"news.faculty_id AS faculty_id",
				"faculty.name AS faculty_name",
				"faculty.p_type AS p_type"
		};
		String whereClause = "news.faculty_id=?";
		String[] faculty_id = this.LoadFaculties(this).toArray(new String[0]);
		for (int i=1;i<faculty_id.length;i++) whereClause += " OR news.faculty_id=?"; // TODO ?? leave hack or make rawSQL statement
		
		dbQuery.setTables("news LEFT OUTER JOIN faculty ON (news.faculty_id = faculty._id)");
		//Cursor cursor = db.query("news", null, whereClause, faculty_id, null, null, "date DESC, _id DESC", null);
		Cursor cursor = dbQuery.query(db, cols, whereClause, faculty_id, null, null, "date DESC, _id DESC", PAGE);
		cursor.moveToFirst();
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		
		return cursor;
	}
	
	public synchronized Cursor readFaculties(String[] filters, boolean isGroupId) {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();
		String colName = "_id";
		if (isGroupId) colName = "group_id";
		
		String[] columns = {"_id", "name", "p_type", "link"};
		String whereClause = colName + "=?";
		for (int i=1;i<filters.length;i++) whereClause += " OR "+ colName +"=?"; // TODO ?? leave hack or make rawSQL statement
		Cursor cursor = db.query("faculty", columns, whereClause, filters, null, null, "group_id ASC, name ASC", null);
		cursor.moveToFirst();
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		
		return cursor;
	}
	
	public synchronized ArrayList<String> LoadFaculties(Context context) {
		final String PREF_NAME = "facultyPrefs";
		String selectedString = null;
		ArrayList<String> selected = new ArrayList<String>();
		

		if (SHARED_PREFERENCES.contains(PREF_NAME)) {
			selectedString = SHARED_PREFERENCES.getString(PREF_NAME, "");
			selected.addAll(Arrays.asList(selectedString.split(",")));
		}
		
		// TODO Re-do Hot Announcement selection
		// TODO Bug: Main Announcements do not work. Different layout
		if (SHARED_PREFERENCES.getBoolean("mainHot", true)) {
			//if(!selected.contains("42")) selected.add("42"); // Add TEIATH's main announcements
			if(!selected.contains("43")) selected.add("43"); // Add TEIATH's HOT announcements
		} else {
			//while(selected.contains("42")) selected.remove("42"); // Remove TEIATH's main announcements
			while(selected.contains("43")) {
				selected.remove("43"); // Remove TEIATH's HOT announcements
				Log.i(tag, "****** Newsfeed APP // BugTrack: Removing 43");
			}

		}

		Log.i(tag, "****** Newsfeed APP // selectedFaculties: " + selected.toString());
		return selected;
	}
	
	/*
	public synchronized void writeNews() {
		// TODO ?? Fetch Service Refactor
	}
	 */

	public synchronized void onSharedPreferenceChanged(SharedPreferences sharedPrefs, String key) {
		
		Log.i(tag, "****** Newsfeed APP // Preference Changed: " + key);
		
		// TODO Test: onChange(mainHot) make changes
		if(key.equals("networkSelect")) PREF_NETWORK_CHANGED = true;
		if(key.equals("updateInterval") || key.equals("autoUpdate")) {
			PREF_ALARM_CHANGED = true;
		}
		NewsList.FLAG_REFRESH = true;
		PREF_CHANGED = true;
	}
	
	public synchronized void preferencesChanged() {
		if(PREF_NETWORK_CHANGED) {
			handleNetworkChange();
			PREF_NETWORK_CHANGED = false;
		}
		
		if(PREF_CHANGED) {
			Intent fetchService = new Intent(this ,FetchService.class);
			this.startService(fetchService);
			
			PREF_CHANGED = false;
		}
		
		if(PREF_ALARM_CHANGED) {
			changeAlarmService();
			PREF_ALARM_CHANGED = false;
		}
	}
	
	public synchronized Feeder newFeed(String parseURL, int pType) {
		Feeder myFeed = new Feeder(parseURL, pType);
		return myFeed;
	}
	
	public synchronized Feeder latestFeeds(String facultyId, String facultyLink, int pType ) {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();
		// TODO Bug: IOException: -1 Unable to load URL on some links.. sometimes..
		String limit = "3"; // Limit to fetch
		
		String[] columns = new String[] { "_id", "name", "date" };
		String whereClause = "faculty_id=?";
		String[] whereArgs = {facultyId};
		
		Cursor cursor = db.query("news", columns, whereClause, whereArgs, null, null, "_id DESC", limit);
		cursor.moveToFirst();
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		
		Feeder myFeed = this.newFeed(facultyLink, pType);
		myFeed.getFeed(); // Get Feed
		myFeed = this.filterFeed(myFeed, cursor);
		
		cursor.close();

		return myFeed;
	}
	
	public synchronized Cursor readContent(String newsId) {
		dbHelper = new DBHelper(this); 
		db = dbHelper.getReadableDatabase();
		//String[] columns = new String[] { "_id", "name", "content", "date" };
		String whereClause = "news._id=?";
		String[] columns = {
				"news._id AS _id", 
				"news.name AS name",
				"news.date AS date",
				"news.content AS content",
				"news._date AS _date",
				"faculty.name AS faculty_name",
				"faculty.p_type AS p_type"
		};
		final SQLiteQueryBuilder dbQuery = new SQLiteQueryBuilder();
		dbQuery.setTables("news LEFT OUTER JOIN faculty ON (news.faculty_id = faculty._id)");
		
		Log.i(tag, "****** Newsfeed APP // Running readContent()");
		
		Cursor cursor = dbQuery.query(db, columns, whereClause, new String[] {newsId}, null, null, null, "1");
		//Cursor cursor = db.query("news", columns, WHERE_ID_IS, new String[] {newsId}, null, null, null, "1");
		cursor.moveToFirst();
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
		
		return cursor;
	}
	
	public synchronized void writeContent(String newsId, String newsContent) {		
		dbHelper = new DBHelper(this); 
		db = dbHelper.getWritableDatabase();
		ContentValues values = new ContentValues();
		values.put("content", newsContent);
		values.put("_content", 1);
		values.put("_external", 0);
		
		Log.i(tag, "****** Newsfeed APP // Running writeContent()");
		
		db.update("news", values, WHERE_ID_IS, new String[] {newsId});
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
	}
	
	public synchronized String refreshContent(String newsId, String parseURL, int pType) {
		Feeder myFeed = this.newFeed(parseURL, pType);
		myFeed.getContent();
		this.writeContent(newsId, myFeed.contentMap.get("content"));
		
		return myFeed.contentMap.get("content");
	}
	
	public synchronized void writeIsExternal(String newsId) {		
		dbHelper = new DBHelper(this); 
		db = dbHelper.getWritableDatabase();

		ContentValues values = new ContentValues();
		values.put("_external", 1);
		//values.put("_content", 0);
		
		Log.i(tag, "****** Newsfeed APP // Running writeIsExternal()");
		
		db.update("news", values, WHERE_ID_IS, new String[] {newsId});
		
		db.close();
		db = null; // 4GC
		dbHelper = null; // 4GC
	}
	
	
	// ?? TODO Async fetchService method?
	public synchronized int fetchService() {
		Cursor faculties;
		Feeder myFeed;
		int newPosts = 0;
		
		String[] filters = this.LoadFaculties(this).toArray(new String[0]); // Get the selected departments from the SharedPrefs
		faculties = this.readFaculties(filters, false);
		faculties.moveToFirst();
		
		
	
		while (!faculties.isAfterLast()) {
			int pType = faculties.getInt(faculties.getColumnIndex("p_type"));
			Log.i(tag, "****** Newsfeed APP // FetchService() Fetching for faculty #" + faculties.getString(faculties.getColumnIndex("_id")));
			
			myFeed = this.latestFeeds(faculties.getString(faculties.getColumnIndex("_id")), faculties.getString(faculties.getColumnIndex("link")), pType); 

			dbHelper = new DBHelper(this);
			db = dbHelper.getWritableDatabase();
			ContentValues values = new ContentValues();
			
			int dCount = 0; // debug
			
			/** 
			 * If feedId =-1 : No New posts!
			 * If feedId = 0 : One new post!
			 * If feedId > 0 : Some new posts!
			 */									
			// Insert into DB with DESC order, sets the _id correctly.
			for ( int i=myFeed.feedId ; i >= 0 ; i-- ) {
				values.clear();
				values.put("name", myFeed.titles.get(i));
				values.put("date", myFeed.dates.get(i));
				values.put("link", myFeed.links.get(i));
				values.put("faculty_id", faculties.getString(faculties.getColumnIndex("_id")));
				db.insertOrThrow("news", null, values);
				dCount++;
			}
			// Close Database
			db.close();
			db = null;	// 4GC	
			dbHelper = null; // 4GC
			
			newPosts += dCount;
			Log.i(tag, "****** Newsfeed APP // FetchService(): " + dCount + " new posts in faculty #" + faculties.getString(faculties.getColumnIndex("_id")));
			faculties.moveToNext();
		}
		faculties.close();
		return newPosts;
	}	
	
	public static long getServiceInterval() {
		long interval = Long.parseLong(SHARED_PREFERENCES.getString("updateInterval", "900000"));
		Log.i(tag, "****** Newsfeed APP // getServiceInterval(): " + (interval/1000)/60 + " minutes");
		return interval;
	}
	
	public static void setAlarmServiceIntent(PendingIntent intent) {
		ALARM_SERVICE_INTENT = intent;
		Log.i(tag, "****** Newsfeed APP // setAlarmServiceIntent(): " + intent);
	}
	
	public static PendingIntent getAlarmServiceIntent() {
		return ALARM_SERVICE_INTENT;
	}
	
	public synchronized void setAlarmService(boolean overrideRun) {	
		final long INTERVAL = getServiceInterval();
		final long FIRST_RUN = SystemClock.elapsedRealtime() + INTERVAL;
		
		if(!overrideRun) {
			if(getAlarmServiceIntent() == null) {				
				Intent serviceIntent = new Intent(this, FetchService.class);
				PendingIntent pendingIntent = PendingIntent.getService(this, -1, serviceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
				
				ALARM_MANAGER.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, FIRST_RUN, INTERVAL, pendingIntent);
				setAlarmServiceIntent(pendingIntent);
				Log.i(tag, "****** Newsfeed APP // AlarmService Set! First Run in " + (INTERVAL / 1000) / 60 + " minutes...");
			} else {
				Log.i(tag, "****** Newsfeed APP // AlarmService already set!");
			}
		} else {
			ALARM_MANAGER.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, FIRST_RUN, INTERVAL, getAlarmServiceIntent());
			Log.i(tag, "****** Newsfeed APP // Overriding AlarmService... First Run in " + (INTERVAL / 1000) / 60 + " minutes...");
		}
	}
	
	public synchronized void changeAlarmService() {	
		final PendingIntent ALARM_INTENT = getAlarmServiceIntent();
		
		Log.i(tag, "****** Newsfeed APP // Changing AlarmService...");
		
		if(SHARED_PREFERENCES.getBoolean("autoUpdate", true)) {
			if(ALARM_INTENT == null) {
				setAlarmService(false);
			} else {
				ALARM_MANAGER.cancel(ALARM_INTENT);
				setAlarmService(true);
			}
		} else {
			if(ALARM_INTENT != null) ALARM_MANAGER.cancel(ALARM_INTENT);
			Log.i(tag, "****** Newsfeed APP // Deleting AlarmService...");
		}
	}
	
	public static void setNetworkConnectivity(boolean isConnected) {
		NETWORK_CONNECTION_ENABLED = isConnected;
	}
	
	public static boolean getNetworkConnectivity() {
		return NETWORK_CONNECTION_ENABLED;
	}
	
	public void handleNetworkChange() {
		final ConnectivityManager CON_MANAGER = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
	    final NetworkInfo NET_INFO = CON_MANAGER.getActiveNetworkInfo();
	    final String PREF_NETWORK = SHARED_PREFERENCES.getString("networkSelect", "WIFI");
	    String currentNetwork;
	   
	    if(NET_INFO != null && NET_INFO.isConnected()) {
	    	currentNetwork = NET_INFO.getTypeName();
	    	if(PREF_NETWORK.equals("ALL") || PREF_NETWORK.equals(currentNetwork)) {
		        setNetworkConnectivity(true);
		        Log.i(tag, "****** FetchService // Connection Type: [" + NET_INFO.getType() + "] "  + currentNetwork);
		        
		        return;
	    	}
	    } 
    	setNetworkConnectivity(false);
    	Log.i(tag, "****** Fetch Service // ... NO CONNECTION ...");	
	}
	
	public void externalLink(String externalURL) {
		if(getNetworkConnectivity()) {
			Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(externalURL));
			browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
	    	startActivity(browserIntent);
		} else {
			Toast.makeText(this, this.getText(R.string.noConnection), Toast.LENGTH_SHORT).show();
		}
	}
	
	
	
	//TODO ?? Needs more work, especially when more faculties are introduced ...
	/** Only find new feeds: Compare 3 latest DB entries (title & date) with feeds to find a pattern */
	private synchronized Feeder filterFeed(Feeder feed, Cursor cur) {
		int feedSize = feed.titles.size() - 1; //Get the feed's size
		
		if (cur.getCount() > 0) { //Check if cursor is empty	
			
			int[] lastFind = {-1, -1, -1}; //Initialisation			
			int matchCount = 0; //Matches found 
			for ( int i=feedSize ; i >= 0 ; i-- ) {
		        cur.moveToFirst();
		        while (!cur.isAfterLast()) {

		            if (cur.getString(cur.getColumnIndex("name")).equals(feed.titles.get(i).toString()) && cur.getString(cur.getColumnIndex("date")).equals(feed.dates.get(i).toString())) {
		            	lastFind[cur.getPosition()] = i;
		            	matchCount++;
		            }
		       	    cur.moveToNext();
		        }
			}
			
			Log.i(tag, "****** filterFeed // Found " + matchCount + "/3 matches!");
			
			if (matchCount > 0) { // If matches are found ...
				Arrays.sort(lastFind); // Sort the array ...
				feedSize = lastFind[0] - 1; // Set lowest ID minus one to feedSize ...
			}
		}
		
		feed.feedId = feedSize; // Set feedSize to feedId and return the feed ...
		return feed;
		
	}
}